#include <stdlib.h>

#include <windows.h>

#include "lib.h"

#include "vphand.h"
#include "vpconn.h"
#include "vpgame.h"
#include "vpguts.h"
#include "vpopts.h"
#include "vpsql.h"
#include "log.h"

#include "stdncerr.h"
#include "badpacketmsgs.h"

#include "MHVPXMLTranslator.h"

VPConnection::VPConnection(Server* server, SOCKET client)
	: Connection(server, client)
{
	flags = 0;
	machId = -1;
	creditValue = 0;
	nHands = 0;
	payTableFreq = 0;
	compFactor = 0;
	game = (VPGame*)0;
	dblUpEnabled = true;
	dlbUpCapAmount = 0;

	casinoSpecific = (void*)0;

	//default subgame data length to zero
	subGameLength = 0;

    m_ucProtoVer1      = 0;
    m_ucProtoVer2      = 0;

    // kharmon.  11-16-06.  #16921.
    msglen = 0;
    memset(msgbuf,0,sizeof(msgbuf));
    #ifdef FORCEDSHOE
    m_pid="";
    #endif
}

VPConnection::~VPConnection()
{
	// save game is always up to date at this point
	//

	delete game;
	game = (VPGame*)0;
	machId = -1;
	creditValue = 0;
	nHands = 0;
	payTableFreq = 0;
	flags &= ~LOGGEDON;

	// CSN-7075: New Authentication
	trackLogoutSession();

	removeCasinoSpecific();
}

void
VPConnection::msgStart()
{
	msglen = 0;
}

void
VPConnection::msgAdd(uint8 Byte)
{
	msgbuf[msglen++] = Byte;
}

void
VPConnection::msgAdd(int32 SignedLong)
{
	msgbuf[msglen++] = (uint8)((SignedLong) & 0xff);
	msgbuf[msglen++] = (uint8)(((SignedLong) >> 8) & 0xff);
	msgbuf[msglen++] = (uint8)(((SignedLong) >> 16) & 0xff);
	msgbuf[msglen++] = (uint8)(((SignedLong) >> 24) & 0xff);
}

uint8*
VPConnection::msgGetBuf()
{
	return msgbuf + msglen;
}

void
VPConnection::msgReleaseBuf(int32 Amt)
{
	msglen += Amt;
}

void
VPConnection::msgEnd()
{
	// message length is 2bytes long !!
	msglen = encode(msgbuf, obuf + oused + 2, msglen);
	msglen++;	// include msg len byte
	msglen++;	// include msg len byte
	obuf[oused] = (uint8)((msglen) & 0xff);
	obuf[oused+1] = (uint8)(((msglen) >> 8) & 0xff);
	oused += msglen;
}

void VPConnection::Answer( uint8 ans )
{
	msgStart();
	msgAdd(ans);
	msgEnd();
}

void VPConnection::Error( int err )
{
	msgStart();
	msgAdd((uint8)'E');
	msgAdd((uint8)(err & 0xff) );
	msgEnd();
}

void
VPConnection::HandlePlayerUnknown()	//Unknown
{
	Log::msgWarning("Connection::getResponse:  Unknown input (0x%02x)  ",
		msgbuf[0]);
		
	trackaddBadPacket(GETREPONSE_DEFAULT_CASE,msgbuf[0],(char*)msgbuf,msglen);

	Answer('N');
}


void
VPConnection::sendStopMsg()
{
	msgStart();
	msgAdd((uint8)'!');
	msgEnd();
}

bool8
VPConnection::getResponse()
{
	int32 ieat = 0, remain = iused, len;
	uint8* src = ibuf;

	// [AO 2007-10-01] We can send just one reply for certain repeating messages.
	// We're not handling partial buffers and incomplete messages but that's OK.
	char last_message = 0;

    // kharmon.  11-16-06.  #16921.
    msglen = 0;
    memset(msgbuf,0,sizeof(msgbuf));

	while (remain > 0)
	{
		if (remain < src[0])
			break;

		// Handle an invalidly formatted message of length zero.
		if (src[0] <= 1)
		{
			ieat += 1;
			remain -= 1;
			src += 1;
			continue;
		}

		len = decode(src + 1, msgbuf, src[0] - 1);
		ieat += src[0];
		remain -= src[0];
		src += src[0];

		switch (msgbuf[0])
		{
			case 'L':	HandlePlayerAnsiLogin	();	break;	//Login
			case 'M':	
			{
				switch (msgbuf[1])
				{
					case 'L':	HandlePlayerUnicodeLogin (); break;	//Login
					default:    HandlePlayerUnknown		 (); break;	//Unknown
				}
				break;
			}

			case 'R':	// Reload with a new denomination (CSN-13423)
			{
				if (!(flags & LOGGEDON))
                {
                    Log::msgWarning("Reload: not logged on... cannot reload with a new denomination  ");

					trackaddBadPacket(TRY_RELOAD_NOT_LOGGED_ON, msgbuf[0],
						(char*)msgbuf, len);

                    Answer('B');
                    break;
                }

                if (isServerStopping())
                {
                    sendStopMsg();
                    break;
                }

				// [AO 2008-01-16] HA: verify the user is still logged on
				if (!ValidateSession())
					break;

				if (game)
				{
					// We have to delete previous game (it was played with a different denomination)
					// If there is a saved game, it will ve recovered later
					delete game;
					game = (VPGame*)0;
				}
				
				HandleReloadDenomination();		
				break;
			}

			case 'D':	// Deal
			{
				if (!(flags & LOGGEDON))
				{
					Log::msgWarning("Deal: not logged on...invalid bet  ");

					trackaddBadPacket(TRY_DEAL_NOT_LOGGED_ON, msgbuf[0],
						(char*)msgbuf, len);

					Answer('B');
					break;
				}

				if (isServerStopping())
				{
					sendStopMsg();
					break;
				}

				// [AO 2008-01-16] HA: verify the user is still logged on
				if (!ValidateSession())
					break;

				int32 bet;
				bet = (((int32)msgbuf[1])&0xFF) +
					(((int32)msgbuf[2] << 8)&0xFF00) +
					(((int32)msgbuf[3] << 16)&0xFF0000);

				if (!game)
				{
					int32 result = VPGame::DEAL_CREATE_GAME_FAILURE;

					game = new VPGame(this, bet, (bool8)(dblUpEnabled != 0));

					if (game && game->initOk())
						result = game->deal();	// creates result msg if successful
					else
					{
						delete game;
						game = (VPGame*)0;
					}

					if (result != VPGame::DEAL_SUCCESS)
					{
						delete game;
						game = (VPGame*)0;

						switch (result)
						{
							case VPGame::DEAL_INVALID_BET:
								Answer('B');
								break;

							case VPGame::DEAL_NOT_ENOUGH_MONEY:
								Answer('$');
								break;

							case VPGame::DEAL_CREATE_GAME_FAILURE:
								Answer('N');
								break;
						}
					}
				}
				else
				{
					DEBUGMSG(("  game in progress  "));
					Answer('G');
				}
				break;
			}

			case 'H':	// Hold
			{
				int32 result = VPGame::HOLD_FAILURE;

				if (!(flags & LOGGEDON))
				{
					Log::msgWarning("Hold: not logged on  ");

					trackaddBadPacket(TRY_HOLD_NOT_LOGGED_ON, msgbuf[0],
						(char*)msgbuf, len);

					Answer('N');
					break;
				}

				// [AO 2008-01-16] HA: verify the user is still logged on
				if (!ValidateSession())
					break;

				if (game)
					result = game->holdThese(msgbuf[1], nHands);	// creates result msg
				else
					trackaddBadPacket(TRY_HOLD_WHEN_NO_GAME, msgbuf[0],
					(char*)msgbuf, len);

				switch (result)
				{
					case VPGame::HOLD_SUCCESS_GAMEOVER:
						gameOver();
						break;

					case VPGame::HOLD_SUCCESS_DBLUP_PENDING:
						//
						// game in progress; results already sent
						//
						break;

					case VPGame::HOLD_FAILURE:
					default:
						Answer('N');
						break;
				}
				break;
			}

			case 'X':	// deny double-up
			{
				if (!(flags & LOGGEDON))
				{
					Log::msgWarning("VP DDU: not logged on  ");

					trackaddBadPacket(TRY_DBLUPDENY_NOT_LOGGED_ON, msgbuf[0],
						(char*)msgbuf, len);

					Answer('N');
					break;
				}

				// [AO 2008-01-16] HA: verify the user is still logged on
				if (!ValidateSession())
					break;

				if (game && game->doubleUpDeny())	// creates result msg
					gameOver();
				else
				{
					if(!game)
						trackaddBadPacket(TRY_DBLUPDENY_NOT_LOGGED_ON,
						msgbuf[0], (char*)msgbuf, len);

					Answer('N');
				}
				break;
			}

			case 'U':	// double-up deal
			{
				if (!(flags & LOGGEDON))
				{
					Log::msgWarning("VP DUD: not logged on  ");

					trackaddBadPacket(TRY_DBLUPDEAL_NOT_LOGGED_ON, msgbuf[0],
						(char*)msgbuf, len);

					Answer('N');
					break;
				}

				// [AO 2008-01-16] HA: verify the user is still logged on
				if (!ValidateSession())
					break;

				if (game && !game->doubleUpDeal())	// creates result msg
				{
					if(!game)
						trackaddBadPacket(TRY_DBLDEAL_WHEN_NO_GAME, msgbuf[0],
							(char*)msgbuf, len);

					Answer('N');
				}
				break;
			}

			case 'u':	// double-up draw
			{
				int32 result = VPGame::DUDRAW_FAILURE;

				if (!(flags & LOGGEDON))
				{
					Log::msgWarning("VP DUW: not logged on  ");

					trackaddBadPacket(TRY_DBLUPDRAW_NOT_LOGGED_ON, msgbuf[0],
						(char*)msgbuf, len);

					Answer('N');
					break;
				}

				// [AO 2008-01-16] HA: verify the user is still logged on
				if (!ValidateSession())
					break;

				if (game)
					result = game->doubleUpDraw(msgbuf[1]);	// creates result msg
				else
					trackaddBadPacket(TRY_DBLDRAW_WHEN_NO_GAME, msgbuf[0],
					(char*)msgbuf, len);

				switch (result)
				{
					case VPGame::DUDRAW_SUCCESS_GAMEOVER:
						gameOver();
						break;

					case VPGame::DUDRAW_SUCCESS_DBLUP_PENDING:
						//
						// game in progress; results already sent
						//
						break;

					case VPGame::DUDRAW_FAILURE:
					default:
						Answer('N');
						break;
				}
				break;
			}

#ifdef ALLOW_HAND_TEST
			case 't':	// 't'est hand
			{
				extern void handtest(uint8*, VPConnection*);
				handtest(msgbuf, this);
				break;
			}
#endif

            case 'm':  // Balance Request
            {
				if (isServerStopping())
	            {
		            sendStopMsg();
		            break;
	            }

				// [AO 2007-10-01] Ignore repeating messages
				// [AO 2008-01-16] HA: verify the user is still logged on
				if (msgbuf[0] == last_message || !ValidateSession())
					break;

	            //rule1: gotta be logged on
	            int32 balance;
				if( (flags & LOGGEDON) && trackGetBalance(&balance) )
                {
                    msgStart();
                    msgAdd((uint8)'m');
                    msgAdd((int32)balance);
                    msgEnd();
                }

	            break;
            }

			default:
				HandlePlayerUnknown();
				break;
		}

		last_message = msgbuf[0]; // [AO 2007-10-01] Handle repeating messages
	}

	if (iused <= ieat) // [AO 2007-10-01] Avoid buffer overflow
		iused = 0;
	else
	{
		MoveMemory(ibuf, ibuf + ieat, iused - ieat);
		iused -= ieat;
	}

	return (bool8)(oused != 0);
}

void
VPConnection::gameOver()
{
	delete game;
	game = (VPGame*)0;

	// if saved game used a different pay table than the current
	// setting for this machine, notify client of new paytable
	//
	if (flags & (PAYTABLECHANGED | DBLUPCHANGED))
	{
		msgStart();
		msgAdd((uint8)'O');		// 'O'ptions
		msgAdd((uint8)((flags & PAYTABLECHANGED) != 0));
		msgAdd((uint8)(payTableFreq & 0xFFL));
		msgAdd((uint8)((flags & DBLUPCHANGED) != 0));
		msgAdd((uint8)(dblUpEnabled != 0));
		msgEnd();

		flags &= ~(PAYTABLECHANGED | DBLUPCHANGED);
	}
}


void
VPConnection::HandlePlayerAnsiLogin()
{
	int32 err = 0;

	if (!parseAnsiCasinoLogin(&err))
	{
		Error(err);
		return;
	}

	HandlePlayerLogin();
}

void
VPConnection::HandlePlayerUnicodeLogin()
{
	int32 err = 0;

	if (!parseUnicodeCasinoLogin(&err))
	{
		Error(err);
		return;
	}

	HandlePlayerLogin();
}

void 
VPConnection::HandleReloadDenomination()	// CSN-13423: change denomination in VPoker
{
	int32 err = 0;

    if (!parseReloadDenomination(&err))
    {
        Error(err);
        return;
    }

    HandlePlayerLogin(true);
}

void
VPConnection::HandlePlayerLogin	(bool isReload)	//Login
{
	int32 err = 0;

	if (isServerStopping())
	{
		machId = -1;
		sendStopMsg();
		return;
	}

	//must not be logged in
	if( (flags & LOGGEDON) )
	{
		// CSN-13423: if we are reloading, we are already logged in
		if (!isReload)
		{			
			Log::msgWarning("Login: attempt to re-log on  ");

			trackaddBadPacket(TRY_LOGON_WHEN_LOGGED_ON,msgbuf[0],(char*)msgbuf,msglen);

			machId = -1;
			Answer('N');
			return;
		}
	}

	//
	//so the user is not logged into the casino
	//
	VPokerOpts opts;

	if (!trackGetOptions(&opts, (uint8)machId,nHands,creditValue) ||
		!trackGetPlayerLimits((uint8)machId, &compFactor))
	{
		Answer('N');

		machId = -1;
		creditValue = 0;
		return;
	}

	payTableFreq = opts.payFreq;
	dblUpEnabled = opts.dblUpEnabled;
	dlbUpCapAmount = opts.dlbUpCapAmount;

	// psharpless #15914  11/01/06
	// Credit values no longer hardcoded

	if (VPGuts::getGuts(machId, payTableFreq) &&
		(creditValue == opts.anCreditValues[0] ||
		 creditValue == opts.anCreditValues[1] || 
		 creditValue == opts.anCreditValues[2] ||
		 creditValue == opts.anCreditValues[3] || 
		 creditValue == opts.anCreditValues[4] ) &&
		(nHands==3 || nHands==10 || nHands==52 || nHands==100) )
	{
		int32 balance;

		if (!trackHasSavedGame(&err) && err)
		{
			Error(err);
			return;
		}

		if (!trackGetBalance(&balance) )
		{
			Answer('N');
			return;
		}

		msgStart();
		if (isReload)
			msgAdd((uint8)'R');
		else
			msgAdd((uint8)'L');
		msgAdd(balance);
		msgAdd((uint8)getMaxBet());
		msgAdd((uint8)(payTableFreq & 0xFFL));

        if(m_ucProtoVer1>=2)
        {
            // we'll send down a list of the supported credit values for games.
            msgAdd(opts.anCreditValues[0]);
            msgAdd(opts.anCreditValues[1]);
            msgAdd(opts.anCreditValues[2]);
            msgAdd(opts.anCreditValues[3]);
            msgAdd(opts.anCreditValues[4]);
        }
		else
		{
			// CSN-13423: flash client (protocol version 1) needs credits as well

			// Fixed number of denominations, as anCreditValues is a fixed array...
			msgAdd((int32)5);			
			msgAdd(opts.anCreditValues[0]);
			msgAdd(opts.anCreditValues[1]);
			msgAdd(opts.anCreditValues[2]);
			msgAdd(opts.anCreditValues[3]);
			msgAdd(opts.anCreditValues[4]);        
		}

		msgAdd((uint8)(game ? 1 : 0));
		if (game)
			game->send();
		msgEnd();

		if (game)
		{
			msgStart();
			msgAdd((uint8)'S');
			for (int i = 0; i<subGameLength && i<MAX_SUBGAME_DATA; i++)
			{
				msgAdd(subGameData[i]);
			}
			msgEnd();
		}

		//
		// prepare to notify client if saved game used a
		// different pay table or dblUpEnabled option than
		// the current setting
		//
		if (game && payTableFreq != game->getPayFreq())
			flags |= PAYTABLECHANGED;
		if (game && dblUpEnabled != game->getDoubleUpEnabled())
			flags |= DBLUPCHANGED;

		flags |= LOGGEDON;
	}
	else
	{
		Log::msgWarning("Login: bad vpoker params: %c, %d",
			machId, creditValue);

		Answer('N');

		machId = -1;
		creditValue = 0;
	}
}

void	
VPConnection::allocateXMLTranslator( XMLTranslator** target )
{
	*target = new MHVPXMLTranslator( getServer(), this );
}

// [AO 2008-01-16] Refactoring for HA
bool VPConnection::ValidateSession()
{
	int32 session_check = trackValidateSession();
	if (!session_check) // session is valid
		return true;

	// [AO 2008-01-22] Additional case from RTG 6.6.0 code drop
	if (session_check == VPokerSQL::NormalLogout)
		session_check = LOGINERR_NOTLOGGEDIN;

	msgStart();
	msgAdd(static_cast<uint8>('N'));
	msgAdd(static_cast<int32>(session_check));
	msgEnd();
	return false;
}
